#include <fstream>
#include <sstream>
#include <algorithm>
#include <stdexcept>
#include <vector>
#include <deque>
#include <string>
#include <iostream>


#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/foreach.hpp>



#define DEBUG_LEVEL 6
#include <debug_func.h>

#include <seq_pairs.h>
#include <get_filename.h>
#include <index_string.h>
#include <portable_timer.h>
#include <progress_indicator.h>
#include <stlhelper.h>
#include <ultoa.h>
#include <print_error.h>
#include <open_fstream.h>
#include <open_stream_name_with_substitutions.h>
#include <crc.h>
#include <digest.h>
#include "get_arg.h"


using std::vector;
using std::deque;
using std::ofstream;
using std::ifstream;
using std::istream;
using std::istringstream;
using std::ostream;
using std::string;
using std::runtime_error;
using std::cerr;
using std::cin;
using std::cout;

//________________________________________________________________________________________

//		get_ids_from_file

//________________________________________________________________________________________
void get_identifiers_from_file(const string& file_path, t_index_string& identifiers, 
							   t_program_args& args)
{
	string file_name = get_filename(file_path);
	args.VERBOSE() << "\tParsing ids from [" << file_name << "]...\n";

	ifstream ifs;
	open_fstream(ifs, file_path);
	unsigned DOTS_SIZE = 100000;
	t_progress_indicator dots(args.VERBOSE(2),  DOTS_SIZE);
	dots.use_timer();
	unsigned cnt_lines = 0;

	string id;
	while (getline(ifs, id))
	{
		++cnt_lines;
		++dots;
		identifiers.index(id);
	}
	dots.finish();
	if (!ifs.eof())
		throw std::runtime_error("Could not read from the file " + file_name);
	if (cnt_lines != identifiers.size())
	{
		args.VERBOSE() << "\t\t" << identifiers.size()
						<< " unique identifiers out of " << cnt_lines
						<< " read from [" << file_name << "].\n";
		args.SUMMARY()	<< identifiers.size()
						<< " unique identifiers out of\n"
						<< cnt_lines
						<< " read from [" << file_name << "].\n";
	}				
	else
	{
		args.VERBOSE(2)	<< "\t\t" << cnt_lines
						<< " identifiers read from [" << file_name << "].\n";
		args.SUMMARY()	<< cnt_lines
						<< " identifiers read from [" << file_name << "].\n";
	}				
}


void open_stream(const string& file_name, ofstream& ostrm)
{
	// file must have name
	if (!file_name.length())
		return;

	// try open
	ostrm.open(file_name.c_str());

	// is open?
	if (!ostrm.good())
		throw std::runtime_error("Could not open the file (" + file_name + ").\n");
}



//________________________________________________________________________________________

//		do_parse_pairs

//________________________________________________________________________________________
struct do_parse_pairs
{
private:
	std::string						comment_letters;
public:
	const t_index_string&			identifiers;
	t_program_args&					args;
	unsigned						cnt_all_lines;
	ofstream						f_both;
	ofstream						f_either;
	ofstream						f_only1;
	ofstream						f_neither;
	unsigned						cnt_both;
	unsigned						cnt_either;
	unsigned						cnt_only1;
	unsigned						cnt_not_matching;
	
	do_parse_pairs(	const t_index_string&				identifiers_,
					t_program_args&						args_,
					const std::string&					comment_letters_):
			comment_letters(comment_letters_),
			identifiers(identifiers_), 
			args(args_),
			cnt_both(0),         
			cnt_either(0),       
			cnt_only1(0),        
			cnt_not_matching(0)
			{
				open_stream(args.file_both,         f_both       );
				open_stream(args.file_either, 		f_either     );
				open_stream(args.file_only1, 		f_only1      );
				open_stream(args.file_neither,		f_neither	 );
			}
	void operator()(istream& is, unsigned cnt_line)
	{
		// get data for one protein pair
		string name1, name2;

		// comment if false
		string comment_line;
		if (!seq_pairs::parse_ids(is, name1, name2, comment_line, comment_letters,
								  seq_pairs::ePARSE_IDS_ONLY))
		{
			f_both	 	<< comment_line;
			f_either 	<< comment_line;
			f_only1	 	<< comment_line;
			f_neither	<< comment_line;
			return;
		}		

		cnt_all_lines = cnt_line;

		string line;
		if (is.peek() == '\t')
		{
			getline(is, line);
			if (!is)
				throw std::runtime_error("The data is not in the correct format");
			// will fail if this is the end of file for either the initial line or the whole
			// 	thing
			is.putback('\n');
		}

		if (args.ids_only)
			line = name1 + '\t' + name2;
		else
			line = name1 + '\t' + name2 + line;


		// split out IDs
		unsigned cnt_matches = 0;
		cnt_matches += identifiers.is_indexed(name1);
		cnt_matches += identifiers.is_indexed(name2);
		switch (cnt_matches)
		{
		case 0:
			f_neither << line << "\n";
			++cnt_not_matching;
			return;
		case 1:
			f_only1	 << line << "\n";
			f_either << line << "\n";
			++cnt_either;
			++cnt_only1;
			return;
		case 2:
			f_both	 << line << "\n";
			f_either << line << "\n";
			++cnt_both;
			++cnt_either;
			return;
		}
	}
};



//________________________________________________________________________________________

//		main

//________________________________________________________________________________________
int main(int argc, char* argv[])
{
	t_program_args args;

	try
	{

	debug_func_cerr(5);

	// don't use C streams
	std::ios::sync_with_stdio(false);


	// get command line arguments
	if (!process_command_line_options(argc, argv, args))
		return 1;


	//
	//	retrieve identifiers
	//
	t_index_string identifiers;
	get_identifiers_from_file(args.file_identifiers, identifiers, args);

	//
	//	print matching
	//
	string file_name = args.file_input.length() ? 
						 get_filename(args.file_input) : 
						 "STDIN";
	args.VERBOSE() << "\tReading sequence pair data from [" + file_name + "]\n";
	do_parse_pairs parse_pairs(identifiers, args,
								args.comment_letters);
	seq_pairs::read_data(parse_pairs, args.file_input, args.VERBOSE());
	args.VERBOSE(1) << "\t\t" << parse_pairs.cnt_all_lines
					<< " lines read from [" << file_name << "].\n";
	args.SUMMARY()	<< parse_pairs.cnt_all_lines
					<< " lines read from [" << file_name << "].\n";
	args.VERBOSE(1) << "\t\t" << parse_pairs.cnt_both
					<< " lines with both identifiers matching.\n";
	args.SUMMARY()	<< parse_pairs.cnt_both
					<< " lines with both identifiers matching.\n";
	args.VERBOSE(1) << "\t\t" << parse_pairs.cnt_either
					<< " lines with either identifier matching.\n";
	args.SUMMARY()	<< parse_pairs.cnt_either
					<< " lines with either identifier matching.\n";
	args.VERBOSE(1) << "\t\t" << parse_pairs.cnt_only1
					<< " lines with only one identifier matching.\n";
	args.SUMMARY()	<< parse_pairs.cnt_only1
					<< " lines with only one identifier matching.\n";
	args.VERBOSE(1) << "\t\t" << parse_pairs.cnt_not_matching
					<< " lines with neither identifier matching.\n";
	args.SUMMARY()	<< parse_pairs.cnt_not_matching
					<< " lines with neither identifier matching.\n";
	
	args.VERBOSE() << "\tAll finished.\n";

	}
	catch (std::exception& e)
	{
		std_print_error(args.CERR(), e.what());
		return 1;
	}

	return 0;
}

